package org.scisaga.tetra.server;


import org.json.JSONObject;
import org.json.JSONException;
import org.json.JSONStringer;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * PORTListenThread
 * @author karajan
 *
 */
public class PORTListenThread extends Thread {

	protected ComputeNode computeNode;
	protected ServerSocket servSock;
	protected static int PORT;
	private volatile boolean done = false;
	
	PORTListenThread(int port, ComputeNode cn){
		this.setName("PORTListenThread");
		computeNode = cn; // A reference of main program
		try {
			PORT = port;
			servSock = new ServerSocket(PORT);
			log("PORTListenThread::Listening on port " + PORT);
		} catch (IOException e) {
			log("IO Exception in <init> : " + e);
			System.exit(0);
		}
	}
	
	@Override
	public void run() {
		try{
			while(!done){
				Socket sock = null;
				sock = servSock.accept();
				String hostName = sock.getInetAddress().getHostName();
				log("Accepted from " + hostName);
				PortHandler handle = new PortHandler(sock,hostName);
				handle.start();
			}
		} catch (IOException e) {
			log("IO Exception : " + e);
			log("*ERROR* quitting...");
			System.exit(0);
		}
	}
	
	public void setDone(){
		done = true;
	}
	
	protected void log(String s){
		this.computeNode.log(s);
	}
	
	/**
	 * PortHandler : responds a remote connection
	 * @author karajan
	 *
	 */
	protected class PortHandler extends Thread {
		protected Socket clntSock;
		protected String clntName;
		protected String clntIp;
		protected BufferedReader is;
		protected OutputStreamWriter osw;
		
		public PortHandler(Socket sock,String clnt) throws IOException {
			
			clntSock = sock;
			clntName = clnt;
			this.setName("PortHandler: from "+clntName);
			is = new BufferedReader(new InputStreamReader(sock.getInputStream(),"8859_1"));
			osw = new OutputStreamWriter(sock.getOutputStream(),"8859_1");
		}
		
		public void run() {
			String line;
			try{
				while((line = is.readLine())!=null){
					JSONObject token = null;
					try {

						token = new JSONObject(line);
						/**
						 * Handle Tasks
						 */
						String type = token.getString("type");
						if(type.equals("TestServ")){
							// Test server status
							osw.write(SysTest.test(token.getString("id")) + "\r\n");
							osw.flush();
						} else if(type.equals("Ping")){
							// Ping available host
							Connector ctor = new Connector(token.getJSONArray("hostList"));
							JSONObject reply = new JSONObject();
							reply.put("id", token.getString("id"));
							reply.put("hostList", ctor.exec());
							
							osw.write(reply.toString() + "\r\n");
							osw.flush();
						} else if(type.equals("LoadEntity")) {
							// 
							if(computeNode.loadEntity(token.getJSONObject("Entity"))){
								osw.write(genResponse(token.getString("id"), "Success") + "\r\n");
							} else osw.write(genResponse(token.getString("id"), "Faild") + "\r\n");
							osw.flush();
							
						} else if(type.equals("PutTokens")) {
							//
							if(computeNode.putTokens(token.getString("EntityId"),token.getJSONArray("Tokens"))){
								osw.write(genResponse(token.getString("id"), "Success") + "\r\n");
							} else osw.write(genResponse(token.getString("id"), "Faild") + "\r\n");
							osw.flush();
							
						} else if(type.equals("GetTokenNum")) {
							// TODO : need fix
							osw.write(computeNode.getTokenNum(token.getString("id"),token.getString("EntityId")) + "\r\n");
							osw.flush();
							
						} else if(type.equals("GetAllTokens")) {
							// TODO : need fix
							String output = computeNode.getAllTokens(token.getString("id"),token.getString("EntityId"));
							osw.write(output + "\r\n");
							osw.flush();
							
						} else if(type.equals("LoadProc")){
							//
							if(computeNode.loadProc(token.getJSONObject("Process"))){
								osw.write(genResponse(token.getString("id"), "Success") + "\r\n");
							} else osw.write(genResponse(token.getString("id"), "Faild") + "\r\n");
							osw.flush();
							
						} else if(type.equals("RunProc")){
							//
							if(computeNode.runProc(token.getString("ProcId"))){
								osw.write(genResponse(token.getString("id"), "Success") + "\r\n");
							} else osw.write(genResponse(token.getString("id"), "Faild") + "\r\n");
							osw.flush();
							
						} else if(type.equals("UnloadProc")){
							if(computeNode.unloadProc(token.getString("ProcId"))){
								osw.write(genResponse(token.getString("id"), "Success") + "\r\n");
							} else osw.write(genResponse(token.getString("id"), "Faild") + "\r\n");
							osw.flush();
							
						} else if(type.equals("SetProcPriority")){
							if(computeNode.setProcPriority(token.getString("ProcId"),token.getInt("Priority"))){
								osw.write(genResponse(token.getString("id"), "Success") + "\r\n");
							} else osw.write(genResponse(token.getString("id"), "Faild") + "\r\n");
							osw.flush();
							
						} else if(type.equals("UnloadEntity")){
							if(computeNode.unloadEntity(token.getString("EntityId"))){
								osw.write(genResponse(token.getString("id"), "Success") + "\r\n");
							} else osw.write(genResponse(token.getString("id"), "Faild") + "\r\n");
							osw.flush();
							
						} else if(type.equals("GetInfo")){
							JSONObject res = new JSONObject();
							res.put("id", token.getString("id"));
							res.put("DBUrl", computeNode.getDBUrl());
							res.put("HostEntity", computeNode.getHostEntity());
							res.put("HostProc", computeNode.getHostProc());
							osw.write(res.toString() + "\r\n");
							osw.flush();
				
						} else if(type.equals("Reset")){
							if(computeNode.unloadAllProc() && computeNode.unloadAllEntities()){
								osw.write(genResponse(token.getString("id"), "Success") + "\r\n");
							} else osw.write(genResponse(token.getString("id"), "Faild") + "\r\n");
							osw.flush();
				
						} else {
							log("ProcHandler::Unknown task type");
							osw.write("*ERROR* Unknown task type \r\n");
							osw.flush();
						}
					} catch (JSONException e) {
						log("Input JSON String is invalid");
					}
				}
				clntSock.close();
			} catch (IOException e){
				log("IO Exception: " + e);
			} finally {
				// Socket is closed
				log("Socket from " + clntName + " closed");
			}
		}
		
		protected String genResponse(String id, String msg){
			String response = "";
			try {
				response = new JSONStringer()
					    .object()
					    	.key("id").value(id)
					    	.key("response").value(msg)
					    .endObject()
					.toString();
			} catch (JSONException e) {
				e.printStackTrace();
			}
			return response;
		}
	}
}
